#include "rasterReader.hpp"
#include "streamAccess.hpp"
#include "exceptions.hpp"

#include "magic_enum.hpp"

#include <optional>
#include <iostream>

namespace hms::raster
{

RasterReader::RasterReader( std::array<std::string, nKeys>&& keysArg ){
	keys( std::move(keysArg) );
}

auto RasterReader::keys() const -> const std::array<std::string, nKeys>& {
	return m_keys;
}

void RasterReader::keys( std::array<std::string, nKeys>&& keysArg ){
	m_keys = toLower( std::move(keysArg) );
}

auto RasterReader::key( Key keyArg ) const -> const std::string& {
	return m_keys[ static_cast<int>(keyArg) ];
}

void RasterReader::key( Key keyArg, std::string keyVal ){
	m_keys[ static_cast<int>(keyArg) ] = hms::toLower( std::move(keyVal) );
}

Raster RasterReader::read( std::istream& is ){
	validate();
	/* the assumption is, that the header part of an ascii file has each line
	 * starting with an alphabetical token, whereas in the body
	 * (i.e. the grid values), each line starts with a numerical value.
	 */
	auto isBody = []( std::stringstream& s ) -> bool {
		scalar testVal;
		if ( s >> testVal )
			return true;
		else {
			s.clear();
			return false;
		}
	};

	auto readKey = [&]( std::stringstream& s )
		-> std::optional<RasterReader::Key>
	{
		std::string str;
		s >> str;
		for ( int i {0}; i<nKeys; ++i ){
			if ( hms::toLower(str) == m_keys[i] )
				return static_cast<Key>(i);
		}
		return std::nullopt;
	};

	Index   nx {0}, ny {0};
	scalar  cellsize {0};
	Array2s bottomLeft;
	bool    useCorners {true};
	scalar  noDataValue { Raster::noDataDefault };

	bottomLeft = Raster::noDataDefault;

	std::string line;
	while ( std::getline(is, line) ){
		std::stringstream conv {line};
		std::streampos eolSize;
		#ifdef _WIN32
		eolSize = 2;
		#else
		eolSize = 1;
		#endif
		if ( isBody(conv) ){
			/* a one pass algorithm would be preferred to seekg() */
			std::streampos cur { is.tellg() };
			is.seekg( cur -
				( static_cast<std::streampos>( line.size() ) + eolSize )
			);
			break;
		}

		std::optional<RasterReader::Key> key { readKey(conv) };

		auto checkEmpty = [&]( auto val, auto defVal ){
			if ( val != defVal )
				throw ParseError(
					"Ambiguating new value for key \""
					+ this->key( key.value() )
					+ "\" found!"
				);
		};
		if ( key ){
			switch ( key.value() ){
				case Key::nx:
					checkEmpty( nx, 0 );
					nx = hms::read<Index>(conv);
					break;
				case Key::ny:
					checkEmpty( ny, 0 );
					ny = hms::read<Index>(conv);
					break;
				case Key::x_corner:
					checkEmpty( bottomLeft.x(), Raster::noDataDefault );
					bottomLeft.x() = hms::read<scalar>(conv);
					break;
				case Key::y_corner:
					checkEmpty( bottomLeft.y(), Raster::noDataDefault );
					bottomLeft.y() = hms::read<scalar>(conv);
					break;
				case Key::x_center:
					checkEmpty( bottomLeft.x(), Raster::noDataDefault );
					bottomLeft.x() = hms::read<scalar>(conv);
					useCorners = true;
					break;
				case Key::y_center:
					checkEmpty( bottomLeft.y(), Raster::noDataDefault );
					bottomLeft.y() = hms::read<scalar>(conv);
					useCorners = true;
					break;
				case Key::cellsize:
					checkEmpty( cellsize, 0 );
					cellsize = hms::read<scalar>(conv);
					break;
				case Key::noData:
					checkEmpty( noDataValue, Raster::noDataDefault );
					noDataValue = hms::read<scalar>(conv);
					break;
				default:
					break;
			}
		}
	}

	auto checkValue = [&]( bool cond, Key keyArg ){
		if (cond)
			throw ParseError(
				"Could not find value for key \"" + key(keyArg) + "\"!"
			);
	};

	checkValue(nx==0, Key::nx);
	checkValue(ny==0, Key::ny);
	checkValue(cellsize==0, Key::cellsize);
	if ( (bottomLeft == Raster::noDataDefault).any() )
		throw ParseError(
			"Could not determine raster origin!"
		);

	/* now resize and read raster values */
	ArrayXXs rasterValues (nx, ny);

	hms::read( is, rasterValues );

	/* the esri ASCII format defines the bottom left cell coordinates, but
	 * starts with the top left cell value. That way, their file looks like
	 * the raster, but it means that here, the values have to be mirrored.  */
	/* also, cols in the raster are rows here and vice versa. */
	if (reverseY)
		rasterValues.rowwise().reverseInPlace();
		
	return {
		std::move(rasterValues),
		cellsize,
		bottomLeft,
		useCorners,
		noDataValue
	};
}

auto RasterReader::toLower( std::array<std::string, nKeys>&& arr )
	-> std::array<std::string, nKeys>
{
	for ( auto& str : arr ){
		hms::toLower(str);
	}
	return arr;
}

void RasterReader::validate() const {
	using magic_enum::enum_name;

	auto checkKey = [&](Key k){
		if ( key(k).empty() )
			throw ParseError(
				"RasterReader: Key \""
				+ std::string{ enum_name(k) }
				+ "\" not defined!"
			);
	};
	checkKey(Key::nx);
	checkKey(Key::ny);
	checkKey(Key::cellsize);
	checkKey(Key::noData);

	if (
		( key(Key::x_corner).empty() || key(Key::y_corner).empty() ) &&
		( key(Key::x_center).empty() || key(Key::y_center).empty() )
	)
		throw ParseError(
			"RasterReader: keys for bottom left corner/cell centre missing!"
		);
}


} // namespace hms::raster
